using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Sockets;
using System.ServiceModel;
using Framework.Core;
using Framework.Remoting.Client;
using Framework.Remoting.Conventions;

namespace Framework.Remoting.Testing
{
    public sealed class IntegrationTest<TContract, TType> : IDisposable
        where TContract : class
        where TType : TContract
    {
        class WcfServiceInfoFactoryForTest : IWcfServiceInfoFactory
        {
            private readonly WcfServiceInfo wcfServiceInfo;

            public WcfServiceInfoFactoryForTest()
            {
                var uri = new Uri(string.Format("net.tcp://localhost:{0}/{1}", FindAvailablePort(), typeof(TContract).FullName));
                var binding = new BindingFactory().Create("netTcpBinding");
                wcfServiceInfo = new WcfServiceInfo(binding, uri);
            }

            [ExcludeFromCodeCoverage]
            public WcfServiceInfo Create(EnvMode envMode, Type contractType)
            {
                throw new NotImplementedException();
            }

            public WcfServiceInfo Create<TContract1>(EnvMode envMode)
            {
                return wcfServiceInfo;
            }
        }

        private readonly TContract proxy;
        private ServiceHost host;

        public IntegrationTest() : this(wcfInterceptorFactory => new ServiceClientFactory(wcfInterceptorFactory))
        {
            
        }

        public IntegrationTest(Func<IWcfInterceptorFactory, IServiceClientFactory> factory)
        {
            var wcfServiceInfoFactoryForTest = new WcfServiceInfoFactoryForTest();
            CreateHost(wcfServiceInfoFactoryForTest.Create<TContract>(EnvMode.Prod));

            proxy = factory(new WcfInterceptorFactory(new ChannelPerRequest(), wcfServiceInfoFactoryForTest)).CreateProxy<TContract>(EnvMode.Prod);
        }

        public TResult Query<TResult>(Func<TContract, TResult> fun)
        {
            return fun(proxy);
        }

        public void Query(Action<TContract> proc)
        {
            proc(proxy);
        }

        public void SimulateCommunicationProblem()
        {
            Close();
        }

        public void Dispose()
        {
            proxy.Close();
            Close();
        }

        private void CreateHost(WcfServiceInfo wcfServiceInfo)
        {
            host = new ServiceHost(typeof(TType), wcfServiceInfo.Uri);

            host.AddServiceEndpoint(typeof(TContract), wcfServiceInfo.Binding, wcfServiceInfo.Uri);
            host.Open();
        }

        private static int FindAvailablePort()
        {
            using (var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
            {
                sock.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0));
                return ((IPEndPoint)sock.LocalEndPoint).Port;
            }
        }

        private void Close()
        {
            host.Close();
        }
    }
}